package com.hrcx.mystery.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.stereotype.Component;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
import java.util.Objects;

/**
 * 如果在使用过程中报错了,那么就需要在进行优化了,到时候参考这篇文章进行优化 : https://www.programminghunter.com/article/56961649566/
 *
 * @author 源源大魔王
 * @date 2021/12/15 16:55
 */
@Slf4j
@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
})
@Component
public class SqlFinalPlugins implements Interceptor {

    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //开始时间
        long startTime = System.currentTimeMillis();
        try {
            return invocation.proceed();
        } finally {
            //计算耗时
            long elapsedTime = System.currentTimeMillis() - startTime;
            //通过 formatSql 获取 SQL 并进行格式化
            String sql = formatSql(invocation);
            printColorString(String.format("SQL耗时: [ %s ] ms \n 执行SQL: [ %s ]", elapsedTime, sql), elapsedTime);
        }
    }

    /**
     * 格式化 SQL 及其参数
     *
     * @param invocation
     * @return 返回格式化后的 SQL
     */
    private String formatSql(Invocation invocation) {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        Object parameter = invocation.getArgs()[1];

        //获取boundSql
        BoundSql boundSql = mappedStatement.getBoundSql(parameter);

        //得到 SQL 语句
        String sql = boundSql.getSql();

        // 输入sql字符串空判断
        if (Objects.isNull(sql)) {
            return "";
        }

        // 美化sql
        sql = beautifySql(sql).toLowerCase();

        // 不传参数的场景，直接把Sql美化一下返回出去
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappings();
        if (Objects.isNull(parameterObject) || parameterMappingList.isEmpty()) {
            return sql;
        }

        // 定义一个没有替换过占位符的sql，用于出异常时返回
        String sqlWithoutReplacePlaceholder = sql;

        try {
            sql = handleCommonParameter(boundSql, mappedStatement);
        } catch (Exception e) {
            System.err.println(e.getMessage());
            // 占位符替换过程中出现异常，则返回没有替换过占位符但是格式美化过的sql
            return sqlWithoutReplacePlaceholder;
        }

        return sql;
    }

    /**
     * 美化 SQL 格式
     *
     * @param sql
     * @return
     */
    private String beautifySql(String sql) {
        sql = sql.replaceAll("[\\s\n ]+", " ");
        return sql;
    }

    /**
     * 替换 SQL 中的 ?,设置 sql 参数
     *
     * @param boundSql
     * @param mappedStatement
     * @return
     */
    private String handleCommonParameter(BoundSql boundSql, MappedStatement mappedStatement) {
        //获取 SQL 语句 , 这个时候的 SQL 语句是没有替换参数的
        String sql = boundSql.getSql();
        //得到入参对象
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        //得到配置
        Configuration configuration = mappedStatement.getConfiguration();
        //得到类型处理器
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        for (ParameterMapping parameterMapping : parameterMappings) {
            if (parameterMapping.getMode() != ParameterMode.OUT) {
                Object value;
                String propertyName = parameterMapping.getProperty();
                if (boundSql.hasAdditionalParameter(propertyName)) {
                    value = boundSql.getAdditionalParameter(propertyName);
                } else if (parameterObject == null) {
                    value = null;
                } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                    value = parameterObject;
                } else {
                    MetaObject metaObject = configuration.newMetaObject(parameterObject);
                    value = metaObject.getValue(propertyName);
                }
                sql = replacePlaceholder(sql, value);
            }
        }
        return sql;
    }

    /**
     * 根据不同的 propertyValue 类型 , 匹配 SQL ? 的类型并替换值
     *
     * @param sql
     * @param propertyValue
     * @return
     */
    private String replacePlaceholder(String sql, Object propertyValue) {
        String value;
        if (Objects.nonNull(propertyValue)) {
            if (propertyValue instanceof String) {
                value = "'" + propertyValue + "'";
            } else if (propertyValue instanceof Date) {
                value = "'" + DATE_TIME_FORMATTER
                        .format(((Date) propertyValue).toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime())
                        + "'";
            } else if (propertyValue instanceof LocalDate) {
                value = "'" + DATE_FORMATTER.format((LocalDate) propertyValue) + "'";
            } else if (propertyValue instanceof LocalDateTime) {
                value = "'" + DATE_TIME_FORMATTER.format((LocalDateTime) propertyValue) + "'";
            } else {
                value = propertyValue.toString();
            }
        } else {
            value = "null";
        }
        return sql.replaceFirst("\\?", value);
    }

    /**
     * 根据不同的超时时间打印不同颜色的字体 :
     * 若超时时间大于默认的超时时间,打印红色字体,否则打印黄色字体
     *
     * @param str
     * @param timeOut
     */
    private void printColorString(String str, Long timeOut) {
        if (timeOut < SqlFinalPlugins.Constant.DEFAULT_TIME_OUT) {
            log.info("-----------------------------------------------------------------------");

            log.info("\033[33;4m" + str + "\033[0m");
            log.info("-----------------------------------------------------------------------");
        } else {
            log.error("-----------------------------------------------------------------------");
            log.error("\033[31;4m" + str + "\033[0m");
            log.error("-----------------------------------------------------------------------");
        }
    }

    /**
     * <p>
     * 内部的常量类，仅供本类使用
     * </p>
     *
     * @author liekkas 2020/12/08 10:52
     */
    private static class Constant {
        public static final Long DEFAULT_TIME_OUT = 500L;
    }

}